/**
    cmd.c.tst - Unit tests for Cmd

    Copyright (c) All Rights Reserved. See details at the end of the file.
 */

/********************************** Includes **********************************/

#include    "testme.h"
#include    "mpr.h"

/*********************************** Locals ***********************************/

typedef struct TestCmd {
    MprCmd  *cmd;
    MprBuf  *buf;
    char    *program;
} TestCmd;

TestCmd *tc;

#define CMD_COUNT 8

static void manageTestCmd(TestCmd *tc, int flags);
static int match(char *s1, char *s2);

/************************************ Code ************************************/

static int initCmd()
{
    tc = mprAllocObj(TestCmd, manageTestCmd);
    ttrue(tc);

    mprAddRoot(tc);
    tc->program = mprJoinPath(".", "runProgram" ME_EXE);
    return 0;
}


static void manageTestCmd(TestCmd *tc, int flags)
{
    if (flags & MPR_MANAGE_MARK) {
        mprMark(tc->cmd);
        mprMark(tc->buf);
        mprMark(tc->program);
    }
}


static void createCmd()
{
    tc->cmd = mprCreateCmd(NULL);
    ttrue(tc->cmd != 0);
    mprDestroyCmd(tc->cmd);
    tc->cmd = 0;
}


static void runCmd()
{
    char    *result, command[ME_MAX_PATH];
    int     status, exitStatus;

    tc->cmd = mprCreateCmd(NULL);
    ttrue(tc->cmd != 0);

    /*
        runProgram reads from the input, so it requires stdin to be connected
     */
    fmt(command, sizeof(command), "%s 0", tc->program);
    status = mprRunCmd(tc->cmd, command, NULL, NULL, &result, NULL, -1, MPR_CMD_IN);
    ttrue(result != NULL);
    ttrue(status == 0);

    exitStatus = mprGetCmdExitStatus(tc->cmd);
    ttrue(exitStatus == 0);

    mprDestroyCmd(tc->cmd);
    tc->cmd = 0;
}


static void withDataCallback(MprCmd *cmd, int channel, void *data)
{
    MprBuf      *buf;
    ssize       len, space;

    ttrue(tc != NULL);
    buf = tc->buf;
    ttrue(buf != NULL);
    len = 0;
    
    switch (channel) {
    case MPR_CMD_STDIN:
        break;

    case MPR_CMD_STDERR:
        mprCloseCmdFd(cmd, channel);
        break;
    
    case MPR_CMD_STDOUT:
        space = mprGetBufSpace(buf);
        if (space < (ME_MAX_BUFFER / 4)) {
            if (mprGrowBuf(buf, ME_MAX_BUFFER) < 0) {
                ttrue(0);
                mprCloseCmdFd(cmd, channel);
                return;
            }
            space = mprGetBufSpace(buf);
        }
        len = mprReadCmd(cmd, channel, mprGetBufEnd(buf), space);            
        if (len <= 0) {
            int status = mprGetOsError();
            mprLog("info mpr test", 5, "Read %zd (errno %d) from %s", len, status, 
                (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr");
            if (len == 0 || (len < 0 && !(status == EAGAIN || status == EWOULDBLOCK))) {
                mprCloseCmdFd(cmd, channel);
            } else {
                mprEnableCmdEvents(cmd, channel);
            }
            return;
            
        } else {
            mprAdjustBufEnd(buf, len);
            mprAddNullToBuf(buf);
            mprEnableCmdEvents(cmd, channel);
        }
        break;

    default:
        /* Child death notification */
        break;
    }
}


/*
    Match s2 as a prefix of s1
 */
static int match(char *s1, char *s2)
{
    int     rc;

    if (s1 == 0) {
        return 0;
    }
    rc = strncmp(s1, s2, strlen(s2));
    return rc;
}


static void withData()
{
    MprBuf      *buf;
    char        *data, *env[16], *argv[16], line[80], *s, *tok;
    ssize       len, rc;
    int         argc, i, status, fd;

    tc->cmd = mprCreateCmd(NULL);
    ttrue(tc->cmd != 0);

    tc->buf = mprCreateBuf(ME_MAX_BUFFER, -1);
    ttrue(tc->buf != 0);

    argc = 0;
    argv[argc++] = tc->program;
    argv[argc++] = "0";
    argv[argc++] = "a";
    argv[argc++] = "b";
    argv[argc++] = "c";
    argv[argc++] = NULL;

    i = 0;
    env[i++] = "CMD_ENV=xyz";
    env[i++] = "USERDOMAIN=localhost";
    env[i++] = NULL;
    mprSetCmdCallback(tc->cmd, withDataCallback, NULL);
    
    rc = mprStartCmd(tc->cmd, argc, (cchar**) argv, (cchar**) env, MPR_CMD_IN | MPR_CMD_OUT | MPR_CMD_ERR);
    ttrue(rc == 0);

    /*
        Write data to the child's stdin. We write so little data, this can't block.
     */
    fd = mprGetCmdFd(tc->cmd, MPR_CMD_STDIN);
    ttrue(fd > 0);

    for (i = 0; i < 10; i++) {
        fmt(line, sizeof(line), "line %d\n", i);
        len = (int) strlen(line);
        rc = mprWriteCmd(tc->cmd, MPR_CMD_STDIN, line, (wsize) len);
        ttrue(rc == len);
        if (rc != len) {
            break;
        }
    }
    mprFinalizeCmd(tc->cmd);

    ttrue(mprWaitForCmd(tc->cmd, TM_SHORT_NAP) == 0);

    /*
        Now analyse returned data
     */
    buf = tc->buf;
    ttrue(mprGetBufLength(buf) > 0);
    if (mprGetBufLength(buf) > 0) {
        data = mprGetBufStart(buf);
        // print("GOT %s", data);
        s = stok(data, "\n\r", &tok);
        ttrue(s != 0);
        ttrue(match(s, "a b c") == 0);

        s = stok(0, "\n\r", &tok);
        ttrue(s != 0);
        ttrue(match(s, "CMD_ENV=xyz") == 0);

        for (i = 0; i < 10; i++) { 
            fmt(line, sizeof(line), "line %d", i);
            s = stok(0, "\n\r", &tok);
            ttrue(s != 0);
            ttrue(match(s, line) == 0);
        }
        s = stok(0, "\n\r", &tok);
        ttrue(match(s, "END") == 0);
    }
    status = mprGetCmdExitStatus(tc->cmd);
    ttrue(status == 0);

    mprDestroyCmd(tc->cmd);
    tc->cmd = 0;
}


static void exitCode()
{
    char        *result, command[ME_MAX_PATH];
    int         i, status;

    tc->cmd = mprCreateCmd(NULL);
    ttrue(tc->cmd != 0);

    for (i = 0; i < 1; i++) {
        fmt(command, sizeof(command), "%s %d", tc->program, i);
        status = mprRunCmd(tc->cmd, command, NULL, NULL, &result, NULL, -1, MPR_CMD_IN);
        ttrue(result != NULL);
        ttrue(status == i);
        if (status != i) {
            mprLog("error mpr test", 0, "Status %d, result %s", status, result);
        }
    }
    mprDestroyCmd(tc->cmd);
    tc->cmd = 0;
}


/*
    Test invoking a command with no stdout/stderr capture.
 */
static void noCapture()
{
    char    command[ME_MAX_PATH];
    int     status;

    tc->cmd = mprCreateCmd(NULL);
    ttrue(tc->cmd != 0);

    fmt(command, sizeof(command), "%s 99", tc->program);
    status = mprRunCmd(tc->cmd, command, NULL, NULL, NULL, NULL, -1, MPR_CMD_IN);
    ttrue(status == 99);

    status = mprGetCmdExitStatus(tc->cmd);
    ttrue(status == 99);
    mprDestroyCmd(tc->cmd);
    tc->cmd = 0;
}


static void multiple()
{
    MprCmd      *cmds[CMD_COUNT];
    char        command[ME_MAX_PATH];
    int         status, i;

    for (i = 0; i < CMD_COUNT; i++) {
        cmds[i] = mprCreateCmd(NULL);
        ttrue(cmds[i] != 0);
        mprAddRoot(cmds[i]);
    }
    for (i = 0; i < CMD_COUNT; i++) {
        fmt(command, sizeof(command), "%s 99", tc->program);
        status = mprRunCmd(cmds[i], command, NULL, NULL, NULL, NULL, -1, MPR_CMD_IN);
        ttrue(status == 99);
        status = mprGetCmdExitStatus(cmds[i]);
        ttrue(status == 99);
    }
    for (i = 0; i < CMD_COUNT; i++) {
        ttrue(cmds[i] != 0);
        mprDestroyCmd(cmds[i]);
        mprRemoveRoot(cmds[i]);
    }
}

static void batch() {
#if ME_WIN_LIKE
    char    *err, *out;
    int     status;

    status = mprRunCmd(tc->cmd, "cmd-batch 1 2 3", NULL, NULL, &out, &err, -1, MPR_CMD_IN);
    ttrue(status == 2);
    ttrue(smatch(out, "Hello Batch 1 2 3\r\n"));

    status = mprRunCmd(tc->cmd, "cmd-cmd 4 5 6", NULL, NULL, &out, &err, -1, MPR_CMD_IN);
    ttrue(status == 3);
    ttrue(smatch(out, "Hello Cmd 4 5 6\r\n"));
#endif
}

static void bang() {
    char    *err, *out;
    int     status;

    status = mprRunCmd(tc->cmd, "cmd-bang 7 8 9", NULL, NULL, &out, &err, -1, MPR_CMD_IN);
    ttrue(status == 4);
    ttrue(sstarts(out, "Hello Bang 7 8 9"));
}


int main(int argc, char **argv)
{
    mprCreate(argc, argv, 0);

    initCmd();
    createCmd();
    runCmd();
    exitCode();
    withData();
    noCapture();
    multiple();
    batch();
    bang();
    return 0;
}

/*
    @copy   default

    Copyright (c) Embedthis Software. All Rights Reserved.

    This software is distributed under commercial and open source licenses.
    You may use the Embedthis Open Source license or you may acquire a 
    commercial license from Embedthis Software. You agree to be fully bound
    by the terms of either license. Consult the LICENSE.md distributed with
    this software for full details and other copyrights.

    Local variables:
    tab-width: 4
    c-basic-offset: 4
    End:
    vim: sw=4 ts=4 expandtab

    @end
 */
